package com.fit4as.fit
{

// Copyright (c) 2002 Cunningham & Cunningham, Inc.
// Released under the terms of the GNU General Public License version 2 or later.

import com.fit4as.util.io.StringWriter;


public class Parse 
{
    public var leader : String;
    public var tag : String;
    public var body : String;
    public var end : String;
    public var trailer : String;

    public var more : Parse;
    public var parts : Parse;

    public static var tags : Array = ["table", "tr", "td"];
    
    public static function createParseFromStrings( tag : String, body : String, parts : Parse = null, more : Parse = null ) : Parse
    {
    	var result : Parse = new Parse();
    	result.leader = "\n";
        result.tag = "<" + tag + ">";
        result.body = body;
        result.end = "</" + tag + ">";
        result.trailer = "";
        result.parts = parts;
        result.more = more;
    	return result;
    }
    
    public static function createParse( text : String, tags : Array = null, level : int = 0, offset : int = 0 ) : Parse
    {
		if( tags == null ) tags = Parse.tags;

        var lc : String = text.toLowerCase();
        var startTag : int = lc.indexOf("<" + tags[level]);
        var endTag : int = lc.indexOf(">", startTag) + 1;
//        var startEnd : int = lc.indexOf("</"+tags[level], endTag);
        var startEnd : int = findMatchingEndTag( lc, endTag, tags[level], offset);
        var endEnd : int = lc.indexOf(">", startEnd) + 1;
        var startMore : int = lc.indexOf("<" + tags[level], endEnd);

        if( startTag < 0 || endTag < 0 || startEnd < 0 || endEnd < 0 ){
            throw new ParseException("Can't find tag: " + tags[ level ], offset );
        }

    	var result : Parse = new Parse();
        result.leader = text.substring( 0, startTag );
        result.tag = text.substring( startTag, endTag );
        result.body = text.substring( endTag, startEnd );
        result.end = text.substring( startEnd, endEnd );
        result.trailer = text.substring( endEnd );
        
        //t race("Parse#body " + body );

        if( level + 1 < tags.length ) 
        {
            result.parts = createParse( result.body, tags, level + 1, offset + endTag );
            result.body = null;
        }
        else 
        { // Check for nested table
			var index : int = result.body.indexOf("<" + tags[0]);
            if( index >= 0 ) 
            {
                //t race("check for nested table " + [ tags[0], index, body ]);
                result.parts = createParse( result.body, tags, 0, offset + endTag );
                result.body = "";
            }
        }

        if( startMore >= 0 ) 
        {
            //t race("startMore " + [tags, level, trailer]);
            result.more = createParse( result.trailer, tags, level, offset + endEnd);
            result.trailer = null;
        }
        return result;
    }

    /* Added by Rick Mugridge, Feb 2005 */
    protected static function findMatchingEndTag( lc : String, matchFromHere : int, tag : String, offset : int ) : int
    {
        //t race("findMatchingEndTag " + [tag, offset, lc]);
        var fromHere : int = matchFromHere;
        var count : int = 1;
        var startEnd : int = 0;
        var guard : int = 100000;
        
        while( --guard > 0 && count > 0 ) 
        {
            var embeddedTag : int = lc.indexOf("<" + tag, fromHere);
            var embeddedTagEnd : int = lc.indexOf("</" + tag, fromHere);

			// Which one is closer?
            if (embeddedTag < 0 && embeddedTagEnd < 0) throw new ParseException("Can't find tag: " + tag, offset);
            if (embeddedTag < 0) embeddedTag = int.MAX_VALUE;
            if (embeddedTagEnd < 0) embeddedTagEnd = int.MAX_VALUE;
            if (embeddedTag < embeddedTagEnd) {
                count++;
                startEnd = embeddedTag;
                fromHere = lc.indexOf(">", embeddedTag) + 1;
            }
            else if (embeddedTagEnd < embeddedTag) {
                count--;
                startEnd = embeddedTagEnd;
                fromHere = lc.indexOf(">", embeddedTagEnd) + 1;
            }
        }
        return startEnd;
    }

    public function size()  : int 
    {
        return more == null ? 1 : more.size() + 1;
    }

    public function last() : Parse 
    {
        return more == null ? this : more.last();
    }

    public function leaf() : Parse 
    {
        return parts == null ? this : parts.leaf();
    }

    public function at( i : int, ... rest ) : Parse 
    {
        if (rest.length == 0) return i == 0 || more == null ? this : more.at( i - 1 );
        if (rest.length == 1)
        {
            var j : int = rest[0];
            return at(i).parts.at(j);
        }
        if (rest.length == 2)
        {
            var j1 : int = rest[0];
            var k : int = rest[1]
            return at(i, j1).parts.at(k);
        }
        throw new Error("Unexpected call to 'at' " + [i, rest]);
    }

    public function text() : String 
    {
        return htmlToText( body );
    }

    public static function htmlToText( s : String ) : String
    {
        s = normalizeLineBreaks( s );
        s = removeNonBreakTags( s );
        s = condenseWhitespace( s );
        s = unescape( s );
        return s;
    }

    private static function removeNonBreakTags( s : String ) : String 
    {
        var i : int = 0;
        var j : int;
        while(( i = s.indexOf('<', i )) >= 0 ){
            if(( j = s.indexOf('>', i + 1 )) == -1 ) break;
            
            if( s.substring( i, j + 1) != "<br />" ) {
                s = s.substring( 0, i ) + s.substring( j + 1 );
            } else {
            	i++;
            }
        }
        return s;
    }

    public static function unescape( s : String ) : String 
    {
        s = s.replace(/<br \/>/g, "\n");
        s = unescapeEntities( s );
        s = unescapeSmartQuotes( s );
        return s;
    }

    private static function unescapeSmartQuotes( s : String ) : String 
    {
        s = s.replace(/\u201c/g, '"');
        s = s.replace(/\u201d/g, '"');
        s = s.replace(/\u2018/g, '\'');
        s = s.replace(/\u2019/g, '\'');
        return s;
    }

    private static function unescapeEntities( s : String ) : String 
    {
        s = s.replace(/&lt;/g, "<");
        s = s.replace(/&gt;/g, ">");
        s = s.replace(/&nbsp;/g, " ");
        s = s.replace(/&quot;/g, "\"");
        s = s.replace(/&amp;/g, "&");
        return s;
    }

    private static function normalizeLineBreaks( s : String ) : String 
    {
        s = s.replace(/<\s*br\s*\/?\s*>/g, "<br />");
        s = s.replace(/<\s*\/\s*p\s*>\s*<\s*p( .*?)?>/g, "<br />");
        return s;
    }

    public static function condenseWhitespace( s : String ) : String 
    {
        s = s.replace(/[ \t\n]+/g, " ");
        s = s.replace(/\u00a0/g, " "); //non breaking space, unicode character 160
        s = s.replace(/&nbsp;/g, " ");
        s = s.replace(/(^\s+)|(\s+$)/g, "");
        return s;
    }

    public function addToTag( text : String ) : void 
    {
        var lastIndex : int = tag.length - 1;
        tag = tag.substring( 0, lastIndex ) + text + ">";
        //t race("Parse#addToTag " + tag);
    }

    public function addToBody( text : String ) : void 
    {
        body = body + text;
    }

    public function print( out : StringWriter ) : void 
    {
        out.println( leader );
        out.println( tag );
        if( parts != null ){
            parts.print( out );
        } else {
            out.println( body );
        }
        out.println( end );
        
        if( more != null ){
            more.print( out );
        } else {
            out.println( trailer );
        }
    }

    public static var footnoteFiles : int = 0;

    public function footnote() : String 
    {
    	throw new Error("not implemented.");
        /*
        if (footnoteFiles>=25) {
            return "[-]";
        } else {
            try {
                var thisFootnote : int = ++footnoteFiles;
                var html : String = "footnotes/" + thisFootnote + ".html";
                var file : Object = new File("Reports/" + html);
                file.delete();
                StringWriter output = new StringWriter(new BufferedWriter(new FileWriter(file)));
                print(output);
                output.close();
                return "<a href=/com.fit4as.fit/Release/Reports/" + html + "> [" + thisFootnote + "]</a>";
            } catch ( e : IOException ) {
                return "[!]";
            }
        }
        */
        return "";
    }
}
}